package com.gitee.wsl.android.ext

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.os.Handler
import android.os.PowerManager
import timber.log.Timber
import android.os.Build
import androidx.annotation.CheckResult
import com.gitee.wsl.ext.coroutines.corbindReceiveChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive

object ReceiverManager {
    private const val TAG = "ReceiverManager"
    private val mReceivers: MutableList<BroadcastReceiver> = ArrayList<BroadcastReceiver>()
    @Synchronized
    fun registerReceiver(
        context: Context,
        receiver: BroadcastReceiver,
        intentFilter: IntentFilter
    ) {
        mReceivers.add(receiver)
        context.registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
        Timber.tag(TAG).v("Registered receiver: " + receiver + " with filter: " + intentFilter)
    }

    @Synchronized
    fun isReceiverRegistered(receiver: BroadcastReceiver): Boolean {
        return mReceivers.contains(receiver)
    }

    @Synchronized
    fun unregisterAllReceivers(context: Context?) {
        if (context == null) {
            Timber.tag(TAG).e("unregisterReceiver: context is null")
            return
        }
        val iter: MutableIterator<BroadcastReceiver> = mReceivers.iterator()
        while (iter.hasNext()) {
            val receiver: BroadcastReceiver = iter.next()
            if (isReceiverRegistered(receiver)) {
                try {
                    context.unregisterReceiver(receiver)
                    Timber.tag(TAG).v("Unregistered receiver: " + receiver)
                } catch (e: IllegalArgumentException) {
                    // We have to catch the race condition a registration is still pending in android
                    // according to https://stackoverflow.com/a/3568906
                    Timber.tag(TAG).w("unregisterReceiver(" + receiver + ") threw IllegalArgumentException")
                }
                iter.remove()
            }
        }
    }


     class BatteryReceiver(val updateShouldRunDecision:()->Unit) : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (Intent.ACTION_POWER_CONNECTED == intent.action || Intent.ACTION_POWER_DISCONNECTED == intent.action) {
                val handler = Handler()
                handler.postDelayed(Runnable { updateShouldRunDecision() }, 5000)
            }
        }
    }


     class NetworkReceiver(val updateShouldRunDecision:()->Unit) : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (ConnectivityManager.CONNECTIVITY_ACTION == intent.action) {
                updateShouldRunDecision()
            }
        }
    }


    class PowerSaveModeChangedReceiver(val updateShouldRunDecision:()->Unit) : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED == intent.action) {
                updateShouldRunDecision()
            }
        }
    }

}

/**
 * Perform an action when the the Intent broadcasts by the selected filter.
 *
 * @param scope Root coroutine scope
 * @param intentFilter Selects the Intent broadcasts to be received
 * @param capacity Capacity of the channel's buffer (no buffer by default)
 * @param action An action to perform
 */
fun Context.receivesBroadcast(
    scope: CoroutineScope,
    intentFilter: IntentFilter,
    capacity: Int = Channel.RENDEZVOUS,
    action: suspend (Intent) -> Unit,
) {
    val events = scope.actor<Intent>(Dispatchers.Main.immediate, capacity) {
        for (intent in channel) action(intent)
    }

    val receiver = receiver(scope, events::trySend)

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
        registerReceiver(receiver, intentFilter)
    } else {
        registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
    }

    events.invokeOnClose { unregisterReceiver(receiver) }
}

/**
 * Perform an action when the the Intent broadcasts by the selected filter, inside new
 * [CoroutineScope].
 *
 * @param intentFilter Selects the Intent broadcasts to be received
 * @param capacity Capacity of the channel's buffer (no buffer by default)
 * @param action An action to perform
 */
suspend fun Context.receivesBroadcast(
    intentFilter: IntentFilter,
    capacity: Int = Channel.RENDEZVOUS,
    action: suspend (Intent) -> Unit,
) = coroutineScope {
    receivesBroadcast(this, intentFilter, capacity, action)
}

/**
 * Create a channel which emits the Intent broadcasts by the selected filter.
 *
 * Example:
 *
 * ```
 * launch {
 *      materialDatePicker
 *          .receivesBroadcast(
 *              scope,
 *              IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
 *          )
 *          .consumeEach { /* handle nfc adapter state changed */ }
 * }
 * ```
 *
 * @param scope Root coroutine scope
 * @param intentFilter Selects the Intent broadcasts to be received
 * @param capacity Capacity of the channel's buffer (no buffer by default)
 */

@CheckResult
fun Context.receivesBroadcast(
    scope: CoroutineScope,
    intentFilter: IntentFilter,
    capacity: Int = Channel.RENDEZVOUS,
): ReceiveChannel<Intent> = corbindReceiveChannel(capacity) {
    val receiver = receiver(scope, ::trySend)

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
        registerReceiver(receiver, intentFilter)
    } else {
        registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
    }

    invokeOnClose { unregisterReceiver(receiver) }
}

/**
 * Create a flow which emits the Intent broadcasts by the selected filter.
 *
 * Example:
 *
 * ```
 * context
 *      .receivesBroadcast(
 *          IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
 *      )
 *      .onEach { /* handle nfc adapter state changed */ }
 *      .flowWithLifecycle(lifecycle)
 *      .launchIn(lifecycleScope) // lifecycle-runtime-ktx
 * ```
 *
 * @param intentFilter Selects the Intent broadcasts to be received
 */
fun Context.receivesBroadcast(intentFilter: IntentFilter): Flow<Intent> = channelFlow {
    val receiver = receiver(this, ::trySend)

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
        registerReceiver(receiver, intentFilter)
    } else {
        registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
    }

    awaitClose { unregisterReceiver(receiver) }
}

@CheckResult
private fun receiver(
    scope: CoroutineScope,
    emitter: (Intent) -> Unit,
) = object : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (scope.isActive) emitter(intent)
    }
}
